﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;

namespace DependencyAnalysis.DataModel
{
    [Serializable]
    [DataContract]
    [KnownType(typeof(MemberDescriptor))]
    [KnownType(typeof(TypeDescriptor))]
    [KnownType(typeof(ModuleDescriptor))]
    [KnownType(typeof(Instruction))]
    public class InstructionCache
    {
        [DataMember(Order = 1)]
        private KeyValuePair<MemberDescriptor, IList<Instruction>>[] _instructionsPerMemberSerialized = null;

        [NonSerialized]
        private Dictionary<MemberDescriptor, IList<Instruction>> _instructionsPerMember = new Dictionary<MemberDescriptor, IList<Instruction>>();

        public IList<Instruction> GetInstructions(MemberDescriptor target)
        {
            if (target.MemberType != MemberType.Method || !_instructionsPerMember.ContainsKey(target)) return new List<Instruction>();
            return _instructionsPerMember[target];
        }

        public void SetInstructions(MemberDescriptor memberDescriptor, IList<Instruction> instructions)
        {
            _instructionsPerMember[memberDescriptor] = instructions;
        }

        public void Serialize()
        {
            _instructionsPerMemberSerialized = _instructionsPerMember.ToArray();
        }

        public void Deserialize()
        {
            if (_instructionsPerMemberSerialized == null)
            {
                return;
            }
            if (_instructionsPerMember == null)
            {
                _instructionsPerMember = new Dictionary<MemberDescriptor, IList<Instruction>>();
            }
            _instructionsPerMember.Clear();
            foreach (var pair in _instructionsPerMemberSerialized)
            {
                if (pair.Key == null || pair.Value == null)
                {
                    continue;
                }
                _instructionsPerMember[pair.Key] = pair.Value;
            }
            _instructionsPerMemberSerialized = null;
        }

        public static readonly ICollection<OperationCode> TypeOpCodes = new HashSet<OperationCode>
            { OperationCode.Castclass,
            OperationCode._Catch, OperationCode.Newarr, OperationCode.Box, OperationCode.Unbox, OperationCode.Unbox_Any,
            OperationCode.Isinst, OperationCode.Initobj, OperationCode.Ldtoken};

        public static readonly ICollection<OperationCode> MethodOpCodes = new HashSet<OperationCode>
            { OperationCode.Call,
            OperationCode.Callvirt, OperationCode.Calli, OperationCode.Newobj, OperationCode.Ldvirtftn, OperationCode.Ldftn,
            OperationCode.Tail_};

        public static readonly ICollection<OperationCode> ConversionOpCodes = new HashSet<OperationCode>
            { OperationCode.Conv_I,
            OperationCode.Conv_I1, OperationCode.Conv_I2, OperationCode.Conv_I4, OperationCode.Conv_I8, OperationCode.Conv_Ovf_I,
            OperationCode.Conv_Ovf_I1, OperationCode.Conv_Ovf_I1_Un,OperationCode.Conv_Ovf_I2,OperationCode.Conv_Ovf_I2_Un,OperationCode.Conv_Ovf_I4,
            OperationCode.Conv_Ovf_I4_Un,OperationCode.Conv_Ovf_I8,OperationCode.Conv_Ovf_I8_Un,OperationCode.Conv_Ovf_I_Un,OperationCode.Conv_Ovf_U,
            OperationCode.Conv_Ovf_U1,OperationCode.Conv_Ovf_U1_Un,OperationCode.Conv_Ovf_U2,OperationCode.Conv_Ovf_U2_Un,OperationCode.Conv_Ovf_U4,
            OperationCode.Conv_Ovf_U4_Un,OperationCode.Conv_Ovf_U8,OperationCode.Conv_Ovf_U8_Un,OperationCode.Conv_Ovf_U_Un,OperationCode.Conv_R4,
            OperationCode.Conv_R8,OperationCode.Conv_R_Un,OperationCode.Conv_U,OperationCode.Conv_U1,OperationCode.Conv_U2,OperationCode.Conv_U4,
            OperationCode.Conv_U8};

        public static readonly ICollection<OperationCode> FieldOpCodes = new HashSet<OperationCode>
            { OperationCode.Ldfld,
            OperationCode.Ldflda, OperationCode.Ldsfld, OperationCode.Ldsflda, OperationCode.Stfld, OperationCode.Ldftn,
            OperationCode.Stsfld, OperationCode.Ldtoken};

        public static readonly ICollection<OperationCode> LocalOpCodes = new HashSet<OperationCode>
        {
            OperationCode.Ldloc, OperationCode.Ldloc_0, OperationCode.Ldloc_1, OperationCode.Ldloc_2, OperationCode.Ldloc_3, OperationCode.Ldloc_S,OperationCode.Ldloca,OperationCode.Ldloca_S,            
            OperationCode.Stloc, OperationCode.Stloc_0, OperationCode.Stloc_1, OperationCode.Stloc_2, OperationCode.Stloc_3, OperationCode.Stloc_S           
        };

        public static readonly ICollection<OperationCode> ArgumentOpCodes = new HashSet<OperationCode>
        {
            OperationCode.Ldarg, OperationCode.Ldarg_0, OperationCode.Ldarg_1, OperationCode.Ldarg_2, OperationCode.Ldarg_3, OperationCode.Ldarg_S, OperationCode.Ldarga
        };

        public static readonly ISet<OperationCode> NestingInstructions = new HashSet<OperationCode> {OperationCode.Brfalse_S,
            OperationCode.Br_S,OperationCode.Br,OperationCode.Brfalse, OperationCode.Brtrue, OperationCode.Brtrue_S, OperationCode.Bne_Un, OperationCode.Bne_Un_S};

        public static readonly ISet<OperationCode> ArithmethicInstructions = new HashSet<OperationCode> { OperationCode.And, OperationCode.Or, OperationCode.Mul, OperationCode.Mul_Ovf,
            OperationCode.Div, OperationCode.Mul_Ovf_Un, OperationCode.Div_Un, OperationCode.Add, OperationCode.Add_Ovf, OperationCode.Add_Ovf_Un, OperationCode.Sub, OperationCode.Sub_Ovf, OperationCode.Sub_Ovf_Un,
            OperationCode.Xor};

        public static readonly ISet<OperationCode> CyclomaticInstructions = new HashSet<OperationCode> { OperationCode.And, OperationCode.Or, OperationCode.Br_S, OperationCode.Brfalse_S,
            OperationCode.Br, OperationCode.Brfalse, OperationCode.Brtrue, OperationCode.Brtrue_S, OperationCode._Catch, OperationCode.Leave, OperationCode.Leave_S, OperationCode.Bne_Un, OperationCode.Bne_Un_S};

        public static readonly ISet<OperationCode> StoreArrayElementInstructions = new HashSet<OperationCode> {
            OperationCode.Stelem, OperationCode.Stelem_I, OperationCode.Stelem_I1, OperationCode.Stelem_I2, OperationCode.Stelem_I4, OperationCode.Stelem_I8, OperationCode.Stelem_R4, OperationCode.Stelem_R8, OperationCode.Stelem_Ref};
    }
}
